#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/select.h>
#include <signal.h>
#include <sys/epoll.h>

int clientArr[1000]; // 用来存放套接字的数组
int count = 0;       // 用来统计有多少客户端连接，有时候也会作为下标使用
#define MAX_EVENT 3000

int main(int c, char *arg[])
{
     struct sockaddr_in sin;
     sin.sin_family = AF_INET;   // IPV4的网络地址版本
     sin.sin_port = htons(9000); // 把9000号端口转为网络字节序并赋值给端口参数

     u_int32_t ip = 0;
     // 把字符串ip地址转为网络字节序ip
     inet_pton(AF_INET, "192.168.50.18", &ip);
     sin.sin_addr.s_addr = ip;

     // 创建一个tcp套接字，参数1指定地址族，参数2指定协议，SOCK_STREAM代表是TCP，SOCK_DGRAM代表UDP
     int sID = socket(AF_INET, SOCK_STREAM, 0);
     int opt = 1;
     if (setsockopt(sID, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
     {
          perror("设置套接字属性出错");
     }
     // 把套接字和地址绑定（手机+手机号）
     if (-1 == bind(sID, (struct sockaddr *)&sin, sizeof(sin)))
     {
          perror("绑定地址失败，请等待30秒或者换一个端口试试");
          return 0;
     }

     // 打开监听功能，监听绑定地址上的来访请求，5代表同时允许5个客户端排队连接
     listen(sID, 5);

     // 定义MAX_EVENT个事件结构体，每个结构体中都有感兴趣的事件和套接字
     struct epoll_event ev, events[MAX_EVENT];

     // 创建epoll对象
     int epoll_id = epoll_create(MAX_EVENT); // 创建一个可以管理MAX_EVENT个套接字事件的epoll对象

     ev.events = EPOLLIN; // 感兴趣的事件为有数据可读
     ev.data.fd = sID;    // 感兴趣的套接字

     // epoll操作和控制，EPOLL_CTL_ADD是往epoll对象中添加结构体
     //该函数内部会复制最后的结构体ev，所以ev可以重复使用
     epoll_ctl(epoll_id, EPOLL_CTL_ADD, sID, &ev);

     while (1)
     {
          // count是当前一共有多少个套集字有可读事件
          // 例如如果当前epoll对象中只有一个套接字有可读事件，那么epoll_wait就会把这个套接字的结构体存入events数组中
          // 而且是按顺序存放的，第一个有事件的套机字结构体就是下标为0的元素
          int count = epoll_wait(epoll_id, events, MAX_EVENT, -1);

          if (count == -1)
          {
               perror("epoll_wait");
               return 0;
          }

          for (size_t i = 0; i < count; i++)
          {
               int fd = events[i].data.fd;
               printf("fd = %d\n",fd);
               if (fd == sID) // 说明i是监听套接字
               {
                    printf("因为i(%lu)就是监听套接字，它上面发生的可读事件是说明有用户来连接我们了\n", i);
                    struct sockaddr_in clientAddr;
                    socklen_t len = sizeof(clientAddr);
                    int newFd = accept(fd, (struct sockaddr *)&clientAddr, &len);
                    printf("产生了一个新的套接字，快为它准一个结构体，并加入到epoll对象中管理起来吧\n");
                    ev.data.fd = newFd;//把新的套接字赋值给结构体
                    // epoll操作和控制，EPOLL_CTL_ADD是往epoll对象中添加结构体
                    if(0==epoll_ctl(epoll_id, EPOLL_CTL_ADD, newFd, &ev))
                    {
                         printf("套接字加入epoll成功\n");
                    }
               }
               else
               {
                    char buf[128];
                    int ret = read(fd, buf, sizeof(buf));
                    if (ret <= 0)
                    {
                         //把要移除的套接字结构体准备好
                         ev.data.fd = fd;
                         //把套接字结构体从epoll中移除掉
                         if(0 == epoll_ctl(epoll_id, EPOLL_CTL_DEL, fd, &ev))
                         {
                              printf("从epoll中移除套接字结构体成功\n");
                         }
                         else
                         {
                              perror("epoll");
                              printf("从epoll中移除套接字结构体失败\n");
                         }
                         close(fd);
                         continue;             // 指向下一个循环
                    }
                    printf("收到客户端的消息：%s\n", buf);
               }
          }
     }
     close(sID);
     return 0;
}